package com.googlecode.phpunit4eclipse.j2phpunit.runner;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;

import org.codehaus.plexus.util.Base64;

/**
 * @author Bertrand Paquet
 */
public class PHPDriver {

	private BufferedReader stdout;
	
	private BufferedReader stderr;

	private BufferedReader in;

	private BufferedWriter out;
	
	private int port;
	
	private ServerSocket server;
	
	private Socket client;
	
	private void openServer() {
		port = 8888;
		while (server == null) {
			try {
				server = new ServerSocket(port, 2, InetAddress.getByName("127.0.0.1"));
				
			} catch (IOException e) {
			}
			if (server == null || !server.isBound()) {
				System.err.println("May be port " + port + " in used, try another");
				port ++;
				server = null;
			}
		}
	}
	
	public PHPDriver(String exec, String workingDirectory, String [] args, int timeout) throws IOException, PHPRunnerException {
		server = null;
		client = null;
		String phpExec = PHPDriverHelper.getInstance().getPhpExec(exec);
		String interpretor = PHPDriverHelper.getInstance().getInterpretor().getAbsolutePath();
		Runtime r = Runtime.getRuntime();
		openServer();
		List<String> cmd = new ArrayList<String>();
		cmd.add(phpExec);
		cmd.add(interpretor);
		cmd.add("--junit-port");
		cmd.add(Integer.toString(port));
		for(String arg : args) {
			cmd.add(arg);
		}
		server.setSoTimeout(timeout * 1000);
		Process process = r.exec(cmd.toArray(new String[]{}), null, new File(workingDirectory));
		try {
			Socket client = server.accept();
			client.setSoTimeout(timeout * 1000);
			in = new BufferedReader(new InputStreamReader(client.getInputStream()));
			out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
			String s = readLine();
			if (s == null || !s.startsWith("Starting interpretor")) {
				throw new IOException("Unable to read interpretor prompt");
			}
		} catch (SocketTimeoutException e) {
			throw new IOException("No connection on socket");
		}
		stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
		stdout = new BufferedReader(new InputStreamReader(process.getInputStream()));
	}
	
	private static String decode(String s) {
		return new String(Base64.decodeBase64(s.getBytes()));
	}

	public void close() throws IOException {
		out.close();
		in.close();
		if (server != null) {
			server.close();			
		}
		if (client != null) {
			client.close();
		}
	}
	
	public String readLine() throws IOException, PHPRunnerException {
		try {
			String s = in.readLine();
			if (s != null) {
				s = decode(s);
			}
			return s;
		} catch (SocketTimeoutException e) {
			throw new PHPRunnerException("PHP Timeout");
		}
	}
	
	public String getStdout() throws IOException {
		return stdout.ready() ? stdout.readLine() : null;
	}
	
	public String getStderr() throws IOException {
		return stderr.ready() ? stderr.readLine() : null;
	}
}
